/*
Borealis Library
A library of commonly used hydrological modeling building blocks

Copyright (C) 2008  Chris Marsh

This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program.  If not, see <http://www.gnu.org/licenses/>.
*/


#include "include/borealis/Observation.hpp"

namespace Borealis
{
	
	void Observation::OpenFile( std::string path )
	{
		std::fstream file(path.c_str());
		std::string line = "";

		int cols = 0;

		//tokenizer
		Borealis::RegexTokenizer token;
		//contains the column headers
		std::vector<std::string> header;

		if(!file.is_open())
			throw std::runtime_error(std::string("Unable to open file ") + path);

		bool done = false;
		token.SetExpression("[^,\\r\\n\\s]+"); //anything but whitespace or ,
		//read in the file, skip any blank lines at the top of the file
		while(!done)
		{
			getline(file,line);
			header = token.Tokenize<std::string>(line);

			//skip any line that is a blank, or that has text in it
			if (header.size() != 0)
				done = true;

		} 
		
		//take that the number of headers is how many columns there should be
		m_cols = header.size();

		for(std::vector<std::string>::const_iterator itr = header.begin();
			itr != header.end();
			itr++)
		{
			ObsTable::const_accessor a;
			if(!m_variables.insert(a,*itr))
			{
				throw std::runtime_error("Failed to insert " + *itr);
			}
		}

				

		//a string defined as anything with a letter in it or that has a special character in this list
		//~`!@#$%^&*(){[}]|\:;"'<>.?/
		//Borealis::RegexTokenizer string("[\\x21\\x22\\x23\\x24\\x25\\x26\\x27\\x28\\x29\\x2A\\x2F\\x3A\\x3B\\x3C\\x3D\\x3E\\x3F\\x40\\x5B\\x5C\\x5D\\x5E\\x5F\\x60\\x7B\\x7C\\x7D\\x88\\x98]|.*[A-Za-z]+.*"); 
		Borealis::RegexTokenizer string(".*"); //anything 
		Borealis::RegexTokenizer integer("^[-+]?\\d+$");
		Borealis::RegexTokenizer floating("^[-+]?(?:[0-9]+\\.(?:[0-9]*)?|\\.[0-9]+)(?:[eE][-+]?[0-9]+)?$");
		Borealis::RegexTokenizer dateTime("[0-9]{8}T[0-9]{6}"); //iso standard time
		
		std::vector<std::string> values; //values on a line


		token.SetExpression("[^,\\r\\n\\s]+"); //anything but whitespace or ,
		
		int lines = 0;
		
		//this is used to save the name of the date header so we can find it to check the timestep later
		std::string dateHeader="";

		while(getline(file,line))
		{
				lines++;
				//how many cols, make sure that equals the number of headers read in.
				unsigned int cols_so_far = 0;
				values = token.Tokenize<std::string>(line);

				//make sure it isn't a blank line
				if(values.size() != 0)
				{
					//get the col name
					std::vector<std::string>::const_iterator headerItr = header.begin();
					
					int cols_so_far = 0;
					//for each column
					for(std::vector<std::string>::const_iterator itr=values.begin();
						itr != values.end();
						itr++)
					{
						std::vector<std::string> strings;
						std::vector<std::string> ints;
						std::vector<std::string> floats;
						std::vector<std::string> dates;

						//check to see if it's an integer first
						if( (ints = integer.Tokenize<std::string>(*itr)).size() == 1)
						{
							ObsTable::accessor a;
							if(!m_variables.find(a,*headerItr))
								throw std::runtime_error("Failed to find " + *headerItr);
							try
							{
								a->second.push_back(boost::lexical_cast<int>(ints[0]));
							}
							catch (...)
							{
								throw std::runtime_error("Failed to cast " + ints[0] + " to an int.");
							}

							
						}
						else if( (floats = floating.Tokenize<std::string>(*itr)).size() ==1 )
						{
							ObsTable::accessor a;
							if(!m_variables.find(a,*headerItr))
								throw std::runtime_error("Failed to find " + *headerItr);
							try
							{
								a->second.push_back(boost::lexical_cast<float>(floats[0]));
							}
							catch (...)
							{
								throw std::runtime_error("Failed to cast " + floats[0] + " to an int.");
							}


						}
 						else if( (dates = dateTime.Tokenize<std::string>(*itr)).size() ==1)
 						{
 							ObsTable::accessor a;

							if(dateHeader == "")
								dateHeader = *headerItr;

 							if(!m_variables.find(a,*headerItr))
 								throw std::runtime_error("Failed to find " + *headerItr);
 
							a->second.push_back(boost::posix_time::from_iso_string(dates[0]));



						}
						//if we get here, it's none of the above, so assume it's a string
						else if( (strings = string.Tokenize<std::string>(*itr)).size() ==1  )
						{
	
							ObsTable::accessor a;
							if(!m_variables.find(a,*headerItr))
								throw std::runtime_error("Failed to find " + *headerItr);
							
							a->second.push_back(strings[0]);
						}	
						else 
						{
							//something has gone horribly wrong
							throw std::runtime_error("Unable to match any regex for " + *itr);
						}

						//next header
						headerItr++;
						cols_so_far++;
						
					}
					if(cols_so_far != m_cols)
					{
						throw std::runtime_error("Failed to read in the correct number of columns on line: " + boost::lexical_cast<std::string>(lines) +
							". Read in: "+boost::lexical_cast<std::string>(cols_so_far)+" ; should have been:" + boost::lexical_cast<std::string>(m_cols));
					}
					m_rows++;
				}
				
		} //end of file read

		m_isOpen = true;

		//check to make sure what we have read in makes sense
		//Check for:
		//	- Each col has the same number of rows
		//	- Time steps are equal

		//get iters for each variables
		std::string* headerItems = new std::string[m_variables.size()];

 		int i = 0;
		//build a list of all the headers
		//unknown order
 		for(ObsTable::iterator itr = m_variables.begin(); itr != m_variables.end(); itr++)
		{
 			headerItems[i++] = itr->first;
 		}

		//get and save each accessor
		bool bfirst = true;
		ObsTable::const_accessor first;


		for(unsigned int l =0; l<m_variables.size(); l++)
		{
			if(bfirst)
			{
				m_variables.find(first,headerItems[0]);
				bfirst = false;
			}
	
			ObsTable::const_accessor a ;
			if(!m_variables.find(a,headerItems[l]))
				throw std::runtime_error("Failed to find " + headerItems[l]);
		
			//check all cols are the same sizes
			if(first->second.size() != a->second.size())
				throw std::runtime_error ("Row " + a->first + " is a different size.");


		}

		first.release();

		delete[] headerItems;
	
	}

	
	Observation::Observation()
	{
		m_cols = 0;
		m_rows = 0;
		m_isOpen = false;
	}

	Observation::~Observation()
	{

	}

	void Observation::ToFile( std::string file )
	{
		std::ofstream out;
		out.open(file.c_str());

		if(!out.is_open())
			throw std::runtime_error("Unable to open file " + file + " for output.");

		std::string* headerItems = new std::string[m_variables.size()];
	
		//build a list of all the headers
		//unknown order
	    int i = 0;
		Variable::const_iterator *tItr = new Variable::const_iterator[m_variables.size()];
		for(ObsTable::iterator itr = m_variables.begin(); itr != m_variables.end(); itr++)
		{
			headerItems[i] = itr->first;
			out << "\t" << itr->first;

			//save vector iterators
			tItr[i] = itr->second.begin();
			i++;
		}
		out << std::endl;


		for(int k=0;k<m_rows;k++)
		{
			for(int j=0; j<m_cols;j++)
			{
				out << "\t" << *(tItr[j]);
				tItr[j]++;
			}
			out << std::endl;
		}

		delete[] tItr;
		delete[] headerItems;
	}

	
	bool Observation::IsOpen()
	{
		return m_isOpen;

	}

	Observation::const_iterator Observation::Begin()
	{
		const_iterator step;
		//build a list of all the headers
		//unknown order

		for(ObsTable::iterator itr = m_variables.begin(); itr != m_variables.end(); itr++)
		{
			Timestep::ConstItrMap::accessor a;
			if(!step.m_currentStep.m_itrs.insert(a,itr->first))
			{
				throw std::runtime_error("Failed to insert " + itr->first);
			}
			a->second = itr->second.begin();

		}

		return step;

	}

	Observation::const_iterator Observation::End()
	{
		const_iterator step;
		//build a list of all the headers
		//unknown order

		for(ObsTable::iterator itr = m_variables.begin(); itr != m_variables.end(); itr++)
		{
			Timestep::ConstItrMap::accessor a;

			if(!step.m_currentStep.m_itrs.insert(a,itr->first))
			{
				throw std::runtime_error("Failed to insert " + itr->first);
			}
			a->second = itr->second.end();

		}

		return step;
	}

	size_t Observation::HashCompare::hash( const std::string& x )
	{
		boost::crc_32_type crc32;
		std::string xlower = boost::algorithm::to_lower_copy<std::string>(x);
		crc32.process_bytes(xlower.c_str(),xlower.length());

		return crc32.checksum();
	}

	bool Observation::HashCompare::equal( const std::string& s1, const std::string& s2 )
	{
		std::string ss1 = boost::algorithm::to_lower_copy<std::string>(s1);
		std::string ss2 = boost::algorithm::to_lower_copy<std::string>(s2);

		return ss1 == ss2;
	}

	const Observation::Timestep& Observation::const_iterator::dereference() const
	{
		return m_currentStep;
	}

	bool Observation::const_iterator::equal(const_iterator const& other) const
	{
		bool isEqual = false;

		//different sizes
		if( m_currentStep.m_itrs.size() != other.m_currentStep.m_itrs.size())
		{
			return false;
		}

		unsigned int i = 0;

		std::string *thisHeaders = new std::string[m_currentStep.m_itrs.size()];
		std::string *otherHeaders = new std::string[m_currentStep.m_itrs.size()];

		Variable::const_iterator *thisIters = new Variable::const_iterator[m_currentStep.m_itrs.size()];
		Variable::const_iterator *otherIters = new Variable::const_iterator[m_currentStep.m_itrs.size()];

		//this' headers and iters
		for(Timestep::ConstItrMap::const_iterator itr = m_currentStep.m_itrs.begin(); itr != m_currentStep.m_itrs.end(); itr++)
		{
			thisHeaders[i] = itr->first;
			thisIters[i] = itr->second; //iterator
			i++;
		}

		i = 0;
		//other's headers and iters
		for(Timestep::ConstItrMap::const_iterator itr = other.m_currentStep.m_itrs.begin(); itr != other.m_currentStep.m_itrs.end(); itr++)
		{
			otherHeaders[i] = itr->first; //key
			otherIters[i] = itr->second; //iterator
			i++;
		}

		for(i = 0; i < m_currentStep.m_itrs.size(); i++)
		{
			for(unsigned int j = 0; j < m_currentStep.m_itrs.size(); j++)
			{
				if(thisHeaders[i] == otherHeaders[j])
				{
					if(thisIters[i] == otherIters[j])
						isEqual = true;
				}
			}
		}

		delete[] thisHeaders;
		delete[] otherHeaders;
		delete[] thisIters;
		delete[] otherIters;
			
		return isEqual;

	}

	void Observation::const_iterator::increment()
	{
		//walks the map locking each node so that the increment can happen
		//walk order is not guaranteed
		unsigned int size = m_currentStep.m_itrs.size();
		std::string *headers = new std::string[size];
		Timestep::ConstItrMap::accessor *accesors = new Timestep::ConstItrMap::accessor[size];
		int i = 0;

		for(Timestep::ConstItrMap::iterator itr = m_currentStep.m_itrs.begin(); itr != m_currentStep.m_itrs.end(); itr++)
		{
			m_currentStep.m_itrs.find(accesors[i],itr->first);
			(accesors[i]->second)++;
			i++;
		}

		delete[] headers;
		delete[] accesors;
		
	}

	void Observation::const_iterator::decrement()
	{
		//walks the map locking each node so that the increment can happen
		//walk order is not guaranteed
		unsigned int size = m_currentStep.m_itrs.size();
		std::string *headers = new std::string[size];
		Timestep::ConstItrMap::accessor *accesors = new Timestep::ConstItrMap::accessor[size];
		int i = 0;

		for(Timestep::ConstItrMap::iterator itr = m_currentStep.m_itrs.begin(); itr != m_currentStep.m_itrs.end(); itr++)
		{
			m_currentStep.m_itrs.find(accesors[i],itr->first);
			(accesors[i]->second)--;
			i++;
		}

		delete[] headers;
		delete[] accesors;
	}

	Observation::const_iterator::const_iterator()
	{

	}

	Observation::const_iterator::const_iterator( const const_iterator& src )
	{
		m_currentStep = Timestep(src.m_currentStep);
	}

	Observation::const_iterator::~const_iterator()
	{

	}

	Observation::const_iterator& Observation::const_iterator::operator=( const Observation::const_iterator& rhs )
	{
		if( this == &rhs)
			return (*this);
		m_currentStep = Timestep(rhs.m_currentStep);
		return *this;
	}

	Observation::Timestep::Timestep( const Timestep& src )
	{
		m_itrs = ConstItrMap(src.m_itrs);
	}

	Observation::Timestep::Timestep()
	{
		
	}

	Observation::Timestep::~Timestep()
	{
		
	}

	std::string Observation::Timestep::ToString()
	{
		std::string s;

		for( Observation::Timestep::ConstItrMap::const_iterator itr = m_itrs.begin(); itr != m_itrs.end(); itr++)
		{
			s += boost::lexical_cast<std::string>(*(itr->second)) + std::string("\t");
		}

		return s;
	}

	int Observation::Timestep::Month()
	{
		int d=-1;
		boost::posix_time::ptime time;
		boost::gregorian::date date;
		

		for( Observation::Timestep::ConstItrMap::const_iterator itr = m_itrs.begin(); itr != m_itrs.end(); itr++)
		{
			try
			{
				time = boost::get<boost::posix_time::ptime>(*(itr->second));
				date = boost::gregorian::from_string(boost::lexical_cast<std::string>(time.date()));
				return date.month();
			}
			catch ( boost::bad_get e)
			{
					//keep going
			}
		
		}

		return d;
	}

	int Observation::Timestep::Day()
	{
		int d=-1;
		boost::posix_time::ptime time;
		boost::gregorian::date date;


		for( Observation::Timestep::ConstItrMap::const_iterator itr = m_itrs.begin(); itr != m_itrs.end(); itr++)
		{
			try
			{
				time = boost::get<boost::posix_time::ptime>(*(itr->second));
				date = boost::gregorian::from_string(boost::lexical_cast<std::string>(time.date()));
				return date.day();
			}
			catch ( boost::bad_get e)
			{
				//keep going
			}

		}

		return d;
	}

	int Observation::Timestep::Year()
	{
		int d=-1;
		boost::posix_time::ptime time;
		boost::gregorian::date date;


		for( Observation::Timestep::ConstItrMap::const_iterator itr = m_itrs.begin(); itr != m_itrs.end(); itr++)
		{
			try
			{
				time = boost::get<boost::posix_time::ptime>(*(itr->second));
				date = boost::gregorian::from_string(boost::lexical_cast<std::string>(time.date()));
				return date.year();
			}
			catch ( boost::bad_get e)
			{
				//keep going
			}

		}

		return d;
	}

	boost::gregorian::date Observation::Timestep::GetGregorian()
	{
		boost::posix_time::ptime time;
		boost::gregorian::date date;

		for( Observation::Timestep::ConstItrMap::const_iterator itr = m_itrs.begin(); itr != m_itrs.end(); itr++)
		{
			try
			{
				time = boost::get<boost::posix_time::ptime>(*(itr->second));
				date = boost::gregorian::from_string(boost::lexical_cast<std::string>(time.date()));
				return date;
			}
			catch ( boost::bad_get e)
			{
				//keep going
			}

		}

		//if we get this far, didn't find it, so bail
		throw std::runtime_error("No date variable found");

	}

	boost::posix_time::ptime Observation::Timestep::GetPosix()
	{
		boost::posix_time::ptime time;


		for( Observation::Timestep::ConstItrMap::const_iterator itr = m_itrs.begin(); itr != m_itrs.end(); itr++)
		{
			try
			{
				time = boost::get<boost::posix_time::ptime>(*(itr->second));
				return time;
			}
			catch ( boost::bad_get e)
			{
				//keep going
			}

		}

		//if we get this far, didn't find it, so bail
		throw std::runtime_error("No date variable found");

	}


	Station::Station()
	{
		m_x = 0;
		m_y = 0;
		m_elevation = 0.0;
		m_obs = NULL;

	}

	Station::Station( std::string ID, std::string file, unsigned int x, unsigned int y, float elevation )
	{
		m_ID = ID;
		m_x = x;
		m_y = y;
		m_elevation = elevation;
		m_obs = NULL; //initialized in openfile
		OpenFile(file);
	}

	void Station::OpenFile( std::string file )
	{
		m_obs = new Observation();
		m_obs->OpenFile(file);

		m_itr = m_obs->Begin();
	}

	Observation::Timestep Station::Now()
	{
		return *m_itr;
	}

	bool Station::Next()
	{
		m_itr++;
		if(m_itr == m_obs->End())
			return false;
		else
			return true;
	}

	unsigned int Station::GetX()
	{
		return m_x;
	}

	unsigned int Station::GetY()
	{
		return m_y;
	}

	void Station::SetX( unsigned int x )
	{
		m_x = x;
	}

	void Station::SetY( unsigned int y )
	{
		m_y = y;
	}

	float Station::GetElevation()
	{
		return m_elevation;
	}

	void Station::SetElevation( float elevation )
	{
		m_elevation = elevation;
	}




}
